#include "StdAfx.h"
#include "Waves.h"
#include "Vertex.h"
#include <vector>
#include <algorithm>


void Waves::init(ID3D10Device *device, DWORD m, DWORD n, float dx, float dt, float speed)
{
	this->md3dDevice = device;
	this->mNumRows = m;
	this->mNumCols = n;
	this->mNumVertices = m*n;
	this->mNumIndices = 6*(m-1)*(n-1);

	this->mStepTime = dt;
	this->mStepSpace = dx;

	this->mDx = dx;
	this->mHalfWidth = (n-1)*dx*0.5f;
	this->mHalfDepth = (m-1)*dx*0.5f;
	
	buildBuffers(device, m, n, dx, dt, speed);
	buildResourceViews();
	buildStates();

}

void Waves::pick(float x, float z) {
	float invDx = 1.0f / mDx;
	DWORD i = int( (mHalfDepth - z) * invDx );
	DWORD j = int( (mHalfWidth + x) * invDx );

	float amp = RandF(0.5f, 1.0f);
	this->disturb(i, j, amp);
}

void Waves::update(float dt)
{
	static float solvT = 0;
	static float distT = 0;
	solvT += dt;
	distT += dt;

	// translate tex
	/*
	this->mTexTranslation.x += this->mTransSpeed * dt;
	this->mTexTranslation.y = this->mTransAmp * sinf(this->mTransRot * this->mTexTranslation.x);
	D3DXMatrixTranslation(&this->mTexMatrix, this->mTexTranslation.x, this->mTexTranslation.y, 0.0f);
	*/

	// invoke a wave
	/*
	if (distT >= this->mDisturbance) {
		distT = 0.0f;
		DWORD i = 5 + (rand() % (this->mNumCols - 10));
		DWORD j = 5 + (rand() % (this->mNumRows - 10));
		//float amp = RandF(0.5f, 1.25f);
		float amp = RandF(0.5f, 1.0f);
		this->disturb(i, j, amp);
	}
	*/

	if (solvT >= this->mStepTime) {
		solvT = 0.0f;

		// step
		for (DWORD i = 1; i < this->mNumRows -1; ++i) {
			for (DWORD j = 1; j < this->mNumCols - 1; ++j) {
				DWORD center = i*this->mNumCols + j;
				DWORD right  = center + 1;
				DWORD left   = center - 1;
				DWORD top    = center - this->mNumCols;
				DWORD bottom = center + this->mNumCols;

				mPrevSolution[center].y = 
					2 * mCurrSolution[center].y
					- mPrevSolution[center].y
					+ this->mK * (
						mCurrSolution[right].y
						+ mCurrSolution[left].y
						+ mCurrSolution[top].y
						+ mCurrSolution[bottom].y
						- 4 * mCurrSolution[center].y );
				mPrevSolution[center].y *= 0.995f;
			}
		}
		// neumann boundary
		for (DWORD i = 1; i < this->mNumRows - 1; ++i) {
			DWORD pos = i*this->mNumCols+0;
			mPrevSolution[pos].y = mCurrSolution[pos+1].y;

			pos = (i+1)*this->mNumCols-1;
			mPrevSolution[pos].y = mCurrSolution[pos-1].y;
		}
		DWORD lastRow = (this->mNumRows - 1) * this->mNumCols;
		for (DWORD i = 0; i < this->mNumCols; ++i) {
			mPrevSolution[i].y         = mCurrSolution[i + this->mNumCols].y;
			mPrevSolution[lastRow+i].y = mCurrSolution[(lastRow+i) - this->mNumCols].y;
		}

		// computer normals
		for (DWORD i = 1; i < this->mNumRows-1; ++i) {
			for (DWORD j = 1; j < this->mNumCols-1; ++j) {
				float left   = this->mCurrSolution[ i   *this->mNumCols+(j-1)].y;
				float right  = this->mCurrSolution[ i   *this->mNumCols+(j+1)].y;
				float top    = this->mCurrSolution[(i-1)*this->mNumCols+ j   ].y;
				float bottom = this->mCurrSolution[(i+1)*this->mNumCols+ j   ].y;

				float fxdx = right - left;
				float fzdz = top - bottom;
				D3DXVECTOR3 &normal = mNormals[i*this->mNumCols+j];
				normal = D3DXVECTOR3(-fxdx, 2*this->mStepSpace, -fzdz);
				D3DXVec3Normalize(&normal, &normal);
			}
		}

		// update the vertex buffer
		std::swap(mPrevSolution, mCurrSolution);
		float du = 1.0f / (this->mNumCols - 1);
		float dv = 1.0f / (this->mNumCols - 1);

		Vertex *vertices = 0;
		HR( this->mVertexBuffer->Map(D3D10_MAP_WRITE_DISCARD, 0, (void**)&vertices) );
		for (DWORD i = 0; i < this->mNumRows; ++i) {
			for (DWORD j = 0; j < this->mNumCols; ++j) {
				DWORD index = i*this->mNumCols+j;
				Vertex &v = vertices[index];
				v.pos	 = this->mCurrSolution[index];
				v.normal = this->mNormals[index];
				v.texC.x = j*du;
				v.texC.y = i*dv;
			}
		}
		this->mVertexBuffer->Unmap();
	}


}

void Waves::draw()
{
	// transparent
	float blendFactor[] = {0.0f, 0.0f, 0.0f, 0.0f};
	md3dDevice->OMSetBlendState(mBlendState, blendFactor, 0xffffffff);

	UINT stride = sizeof(Vertex);
	UINT offset = 0;
	this->md3dDevice->IASetVertexBuffers(0, 1, &mVertexBuffer, &stride, &offset);
	this->md3dDevice->IASetIndexBuffer(mIndexBuffer, DXGI_FORMAT_R32_UINT, 0);
	this->md3dDevice->DrawIndexed(this->mNumIndices, 0, 0);
}

D3DXMATRIX	Waves::getWorldMatrix()
{
	return mWorldMatrix;
}

D3DXMATRIX	Waves::getTexMatrix()
{
	return mTexMatrix;
}

ID3D10ShaderResourceView* Waves::getDiffuseMap(ID3D10ShaderResourceView* defaultMap)
{
	if (mDiffuseMap == 0)
		return defaultMap;
	return mDiffuseMap;
}

ID3D10ShaderResourceView* Waves::getSpecMap(ID3D10ShaderResourceView* defaultMap)
{
	if (mSpecMap == 0)
		return defaultMap;
	return mSpecMap;
}

void Waves::buildBuffers(ID3D10Device *device, DWORD m, DWORD n, float dx, float dt, float speed)
{
	float c = (speed*speed) * (dt*dt) / (dx*dx);
	mK		= c;

	mPrevSolution = new D3DXVECTOR3[this->mNumVertices];
	mCurrSolution = new D3DXVECTOR3[this->mNumVertices];
	mNormals      = new D3DXVECTOR3[this->mNumVertices];

	// vertices
	float halfWidth = (n-1)*dx*0.5f;
	float halfDepth = (m-1)*dx*0.5f;
	for (DWORD i = 0; i < m; ++i) {
		float z = halfDepth - i*dx;
		
		for (DWORD j = 0; j < n; ++j) {
			float x = -halfWidth + j*dx;

			mPrevSolution[i*n+j] = D3DXVECTOR3(x, 0.0f, z);
			mCurrSolution[i*n+j] = D3DXVECTOR3(x, 0.0f, z);
			mNormals[i*n+j]		 = D3DXVECTOR3(0.0f, 1.0f, 0.0f);
		}
	}

	// vertex buffer
	D3D10_BUFFER_DESC vbd;
	ZeroMemory( &vbd, sizeof(vbd) );
	vbd.Usage = D3D10_USAGE_DYNAMIC;
	vbd.ByteWidth = sizeof(Vertex) * this->mNumVertices;
	vbd.BindFlags = D3D10_BIND_VERTEX_BUFFER;
	vbd.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;
	HR( this->md3dDevice->CreateBuffer(&vbd, 0, &mVertexBuffer) );

	// indices
	std::vector<DWORD> indices(this->mNumIndices);
	int k = 0;
	for (DWORD i = 0; i < m-1; ++i) {
		for (DWORD j = 0; j < n-1; ++j) {
			indices[k++] =  i   *n+ j;
			indices[k++] =  i   *n+(j+1);
			indices[k++] = (i+1)*n+ j;
			indices[k++] = (i+1)*n+ j;
			indices[k++] =  i   *n+(j+1);
			indices[k++] = (i+1)*n+(j+1);
		}
	}

	// index buffer
	D3D10_BUFFER_DESC ibd;
	ZeroMemory( &ibd, sizeof(ibd) );
	ibd.Usage = D3D10_USAGE_IMMUTABLE;
	ibd.ByteWidth = sizeof(DWORD) * this->mNumIndices;
	ibd.BindFlags = D3D10_BIND_INDEX_BUFFER;
	D3D10_SUBRESOURCE_DATA iInitData;
	iInitData.pSysMem = &indices[0];
	HR( this->md3dDevice->CreateBuffer(&ibd, &iInitData, &mIndexBuffer) );
}

void Waves::buildResourceViews()
{
	//this->mDiffuseMap = GetTextureMgr().createTex( L"waterA192.dds" );
	//this->mDiffuseMap = GetTextureMgr().createTex( L"water2a.dds" );
	this->mDiffuseMap = GetTextureMgr().createTex( L"water010.dds" );
	this->mSpecMap = GetTextureMgr().createTex( L"graySpecular018.dds" );
}

void Waves::buildStates()
{
	D3D10_BLEND_DESC blendDesc = {0};
	blendDesc.AlphaToCoverageEnable = false;
	blendDesc.BlendEnable[0] = true;
	blendDesc.SrcBlend       = D3D10_BLEND_SRC_ALPHA;
	blendDesc.DestBlend      = D3D10_BLEND_INV_SRC_ALPHA;
	blendDesc.BlendOp        = D3D10_BLEND_OP_ADD;
	blendDesc.SrcBlendAlpha  = D3D10_BLEND_ONE;
	blendDesc.DestBlendAlpha = D3D10_BLEND_ZERO;
	blendDesc.BlendOpAlpha   = D3D10_BLEND_OP_ADD;
	blendDesc.RenderTargetWriteMask[0] = D3D10_COLOR_WRITE_ENABLE_ALL;

	HR( this->md3dDevice->CreateBlendState(&blendDesc, &mBlendState) );
}


void Waves::disturb(DWORD i, DWORD j, float magnitude)
{
	// Don't disturb boundaries.
	//assert(i > 1 && i < mNumRows-2);
	//assert(j > 1 && j < mNumCols-2);
	if (i <= 1)
		i = 2;
	else if (mNumRows-2 <= i)
		i = mNumRows-3;

	if (j <= 1)
		j = 2;
	else if (mNumRows-2 <= j)
		j = mNumRows-3;

	float halfMag = 0.5f*magnitude;

	mCurrSolution[ i   *this->mNumCols+j  ].y += magnitude;
	mCurrSolution[ i   *this->mNumCols+j+1].y += halfMag;
	mCurrSolution[ i   *this->mNumCols+j-1].y += halfMag;
	mCurrSolution[(i+1)*this->mNumCols+j  ].y += halfMag;
	mCurrSolution[(i-1)*this->mNumCols+j  ].y += halfMag;
}
